/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/encode.c,v 1.14 2002/09/17 13:24:32 bboy Exp $
	encode.c: Encode/decode DNS-encoded names.

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"

#undef  DEBUG_ENCODING

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	NAME_REMEMBER
	Adds the specified name + offset to the `Labels' array within the specified task.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
inline void
name_remember(t, name, offset)
	TASK *t;
	u_char *name;
	u_int offset;
{
	if (!name || strlen(name) > 64)						/* Don't store labels > 64 bytes in length */
		return;

	if (!t->Names)
	{
		if (!(t->Names = malloc(sizeof(u_char *))))
			Err("malloc");
	}
	else
	{
		if (!(t->Names = realloc(t->Names, (t->numNames + 1) * sizeof(u_char *))))
			Err("realloc");
	}

	if (!(t->Names[t->numNames] = strdup(name)))
		Err("strdup");

	if (!t->Offsets)
	{
		if (!(t->Offsets = malloc(sizeof(u_int))))
			Err("malloc");
	}
	else
	{
		if (!(t->Offsets = realloc(t->Offsets, (t->numNames + 1) * sizeof(u_int))))
			Err("realloc");
	}
#ifdef DEBUG_ENCODING
	Notice("remembering name \"%s\" at offset %u", name, offset);
#endif
	t->Offsets[t->numNames] = offset;
	t->numNames++;
}
/*--- name_remember() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	NAME_FORGET
	Forget all names in the specified task.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
inline void
name_forget(t)
	TASK *t;
{
	register int n;

	for (n = 0; n < t->numNames; n++)
		free(t->Names[n]);
	if (t->Names) free(t->Names);
	t->Names = NULL;
	t->numNames = 0;
	if (t->Offsets) free(t->Offsets);
	t->Offsets = NULL;
}
/*--- name_forget() -----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	NAME_FIND
	Searches the task's remembered names arary for `name'.
	Returns the offset within the reply if found, or 0 if not found.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
u_int
name_find(t, name)
	TASK *t;
	u_char *name;
{
	register u_int n;

	for (n = 0; n < t->numNames; n++)
		if (!strcasecmp(t->Names[n], name))
		{
#ifdef DEBUG_ENCODING
			Notice("found name \"%s\" at offset %u", name, t->Offsets[n]);
#endif
			return (t->Offsets[n]);
		}
	return (0);
}
/*--- name_find() -------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	NAME_ENCODE
	Encodes `in_name' into `dest'.  Returns the length of data in `dest', or -1 on error.
	If `name' is not NULL, it should be DNS_MAXNAMELEN bytes or bigger.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
name_encode(TASK *t, u_char *dest, u_char *name, u_int dest_offset)
{
	u_char namebuf[DNS_MAXNAMELEN+1];
	register u_char *c, *d, *this_name, *cp;
	register int len = 0;
	register u_int offset;

#ifdef DEBUG_ENCODING
	Notice("encoding \"%s\" at dest offset %u", name, dest_offset);
#endif

	strncpy(namebuf, name, sizeof(namebuf)-1);

	/* Examine name one label at a time */
	for (c = namebuf, d = dest; *c; c++)
		if (c == namebuf || *c == '.')
		{
			if (!c[1])
			{
				len++;
				if (len > DNS_MAXNAMELEN)
					return dnserror(t, DNS_R_FORMERR, ERR_RR_NAME_TOO_LONG);
				*d++ = 0;
				return (len);
			}
			this_name = (c == namebuf) ? c : (++c);
#ifndef NO_ENCODING
			if (!t->no_markers && (offset = name_find(t, this_name)))
			{
				/* Found marker for this name - output offset pointer and we're done */
				len += SIZE16;
				if (len > DNS_MAXNAMELEN)
					return dnserror(t, DNS_R_FORMERR, ERR_RR_NAME_TOO_LONG);
				offset |= 0xC000;
				DNS_PUT16(d, offset);
				return (len);
			}
			else		/* No marker for this name; encode current label and store marker */
#endif
			{
				register u_int nlen;

				if ((cp = strchr(this_name, '.')))
					*cp = '\0';
				nlen = strlen(this_name);
				len += nlen + 1;
				if (len > DNS_MAXNAMELEN)
					return dnserror(t, DNS_R_FORMERR, ERR_RR_NAME_TOO_LONG);
				*d++ = (u_char)nlen;
				memcpy(d, this_name, nlen);
				d += nlen;
				if (cp)
					*cp = '.';
				if (!t->no_markers)
					name_remember(t, this_name, dest_offset + (c - namebuf));
			}
		}
	return (len);
}
/*--- name_encode() -----------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
